﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Speech.Recognition;
using System.Runtime.InteropServices;
using NppPluginNET.Speech;
using System.Speech.Synthesis;
using System.Globalization;
using System.Text.RegularExpressions;


namespace NppPluginNET
{
    public class SpeechManager
    {

        private const float MINIMUM_CONFIDENCE = 0.8f;

        private SpeechRecognitionEngine sr;
        private Grammar grammar;
        public event EventHandler<CodeGenerationEventArgs> CodeGenerationRecognized;
        public event EventHandler<EditorControlEventArgs> EditorNavigationRecognized;
        public event EventHandler<EditorControlEventArgs> TextManipulationRecognized;
        private PluginExtensionLoader extensionLoader;
        private PluginExtensionParser extensionParser;

        public LangType LanguageType { get; set; }

        public SpeechManager()
        {
            extensionLoader = new PluginExtensionLoader();
        }

        public void Initialize()
        {
          try
          { 
            //init parser
            extensionLoader.LoadLanguageExtensions();
            extensionLoader.LoadSpeechExtensions();
            extensionParser = new PluginExtensionParser(extensionLoader.ExtensionLanguages, extensionLoader.SpeechExtensions);
            //init speech
            sr = new SpeechRecognitionEngine(new CultureInfo("en-US"));
            sr.SetInputToDefaultAudioDevice();
            grammar = new Grammar(@"Grammar\Grammar.xml");
            sr.LoadGrammar(grammar);
            //load speech extension grammar
            if (extensionLoader.SpeechExtensions.Count != 0)
                sr.LoadGrammar(CreateSpeechExtensionGrammar(extensionLoader.SpeechExtensions));
            sr.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(SpeechRecognized);
            sr.RecognizeAsync(RecognizeMode.Multiple);
          }
          catch(Exception e)
          {
            //TODO : Handle Plugin Exception
          }
        }

        private Grammar CreateSpeechExtensionGrammar(Dictionary<string, string> dictionary)
        {
            List<string> phrases = new List<string>();
            foreach (var pair in dictionary)
            {
                phrases.Add(pair.Key);
            }
            return new Grammar(new GrammarBuilder(new Choices(phrases.ToArray())));
        }

        public void Dispose()
        {
            if (sr != null)
            {
                sr.Dispose();
            }
        }

        public void Speak(string textToSpeak)
        {
            SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer();
            speechSynthesizer.Speak(textToSpeak);
            speechSynthesizer.Dispose();
        }

        private string FindClassName()
        {
          string text = NPPPlugin.GetCurrentDocumentText();
          string className = "";

          Match m = Regex.Match(text, @"public\s+class\s+(\w+)");
          if (m.Success)
            className = m.Groups[1].Value;

          return className;

        }

        private Dictionary<string, string> FindMembers()
        {
          string text = NPPPlugin.GetCurrentDocumentText();
          Dictionary<string, string> members = new Dictionary<string, string>();

          MatchCollection matches = Regex.Matches(text, @"(private|protected)\s+(\w+)\s+(\w+)\s*;");
          foreach (Match m in matches)
          {
            if (m.Success)
            {
              members.Add(m.Groups[3].Value, m.Groups[2].Value);
            }
          }

          return members;
        }

        void SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
          try
          {
            if (e.Result != null && e.Result.Confidence > MINIMUM_CONFIDENCE)
            {
                //handle speech extensions
                string result;
                if (extensionLoader.SpeechExtensions.TryGetValue(e.Result.Text, out result))
                {
                    ParseResult parseResult = new ParseResult(result, result.Length);
                    CodeGenerationRecognized(this, new CodeGenerationEventArgs(parseResult));
                    return;
                }
                //handle main grammar
                SemanticValue semantics = e.Result.Semantics;
                if (semantics != null && semantics.Count != 0)
                {
                    SemanticValue type = null;
                    if (TryGetSemanticValue(semantics, "CodeGeneration", out type))
                    {
                        if (TryGetSemanticValue(type, "Statement", out type))
                        {
                            try
                            {
                                HandleStatementRecognized(type, LanguageType);
                            }
                            catch(PluginExtensionExeption ex)
                            {
                                // Atm do nothing when exception happend in plugin extension parser
                                CodeGenerationRecognized(this, new CodeGenerationEventArgs(new ParseResult("##Language not supported##",0)));
                            }
                        }
                    }
                    else if (TryGetSemanticValue(semantics, "EditorControl", out type))
                    {
                        SemanticValue controlType;
                        if (TryGetSemanticValue(type, "EditorNavigation", out controlType))
                        {
                            HandleEditorNavigationRecognized(controlType);
                        }
                        else if (TryGetSemanticValue(type, "TextManipulation", out controlType))
                        {
                            HandleTextManipulationRecognized(controlType);
                        }
                    }
                }            
            }
          }
          catch (Exception exep)
          {
            //TODO : Handle Plugin Exception
          }
        }

        private void HandleStatementRecognized(SemanticValue semantics, LangType language)
        {
            SemanticValue type;
            if (TryGetSemanticValue(semantics, "Type", out type))
            {
                if (type.Value.Equals("If"))
                {
                    ParseResult parseResult;
                    if (TryGetSemanticValue(semantics, "Param", out type) && "Else".Equals(type.Value.ToString()))
                    {
                        parseResult = extensionParser.ParseIfElseText(language);
                    }
                    else
                    {
                        parseResult = extensionParser.ParseIfText(language);
                    }
                    CodeGenerationRecognized(this, new CodeGenerationEventArgs(parseResult));
                }
                else if (type.Value.Equals("For"))
                {
                    ParseResult parseResult = extensionParser.ParseForText(language);
                    CodeGenerationRecognized(this, new CodeGenerationEventArgs(parseResult));
                }
                else if (type.Value.Equals("While"))
                {
                    ParseResult parseResult = extensionParser.ParseWhileText(language);
                    CodeGenerationRecognized(this, new CodeGenerationEventArgs(parseResult));
                }
                else if (type.Value.Equals("Class")) {      
                    String name = String.Empty;
                    String access = String.Empty;
                    if (TryGetSemanticValue(semantics, "Ident", out type)) {
                        name = type.Value.ToString();
                    }
                    if (TryGetSemanticValue(semantics, "AccessLevel", out type))
                    {
                        access = type.Value.ToString();
                    }
                    ParseResult parseResult = extensionParser.ParseClassText(language, access, name);
                    CodeGenerationRecognized(this, new CodeGenerationEventArgs(parseResult));
                }
                else if (type.Value.Equals("GetterSetter"))
                {
                  Dictionary<string, string> members = FindMembers();
                  string text = "";
                  foreach (var member in members)
                  {
                    ParseResult parseResult = extensionParser.ParseGetterSetterText(language, member.Value, member.Key);
                    text += parseResult.Text;      
                  }
                  CodeGenerationRecognized(this, new CodeGenerationEventArgs(new ParseResult(text,text.Length)));
                }
                else if (type.Value.Equals("Ctor"))
                {
                    string className = FindClassName();
                    ParseResult parseResult = extensionParser.ParseConstructorText(language, className);
                    CodeGenerationRecognized(this, new CodeGenerationEventArgs(parseResult));
                }
                else if (type.Value.Equals("Function"))
                {
                    String name = String.Empty;
                    String access = String.Empty;
                    String returnType = String.Empty;
                    if (TryGetSemanticValue(semantics, "Ident", out type))
                    {
                        name = type.Value.ToString();
                    }
                    if (TryGetSemanticValue(semantics, "AccessLevel", out type))
                    {
                        access = type.Value.ToString();
                    }
                    if (TryGetSemanticValue(semantics, "DataType", out type))
                    {
                        access = type.Value.ToString();
                    }
                    ParseResult parseResult = extensionParser.ParseFunctionText(language, access, returnType, name);
                    CodeGenerationRecognized(this, new CodeGenerationEventArgs(parseResult));
                }
            }
        }

        private void HandleEditorNavigationRecognized(SemanticValue semantics)
        {
            EditorControlEventArgs ecea = CreateEditorControlEventArgs(semantics);
            if (ecea != null)
            {
                EditorNavigationRecognized(this, ecea);
            }
        }

        private void HandleTextManipulationRecognized(SemanticValue semantics)
        {
            EditorControlEventArgs ecea = CreateEditorControlEventArgs(semantics);
            if (ecea != null)
            {
                TextManipulationRecognized(this, ecea);
            }
        }

        private static IDictionary<EditorControl, IList<ControlScope>> editorControlMap = new Dictionary<EditorControl, IList<ControlScope>>()
        {
            {EditorControl.GoTo, new List<ControlScope>() {ControlScope.Line, ControlScope.Tab}},
            {EditorControl.New, new List<ControlScope>() {ControlScope.Tab, ControlScope.File}},
            {EditorControl.Close, new List<ControlScope>() {ControlScope.Tab, ControlScope.NotePad, ControlScope.None}},
            {EditorControl.Find, new List<ControlScope>() {ControlScope.None}},
            {EditorControl.Replace, new List<ControlScope>() {ControlScope.None}},
            {EditorControl.Save, new List<ControlScope>() {ControlScope.File, ControlScope.All, ControlScope.As, ControlScope.None}},
            {EditorControl.Select, new List<ControlScope>() {ControlScope.All, ControlScope.Line}},
            {EditorControl.Delete, new List<ControlScope>() {ControlScope.All, ControlScope.Line}},
            {EditorControl.Copy, new List<ControlScope>() {ControlScope.None}},
            {EditorControl.Paste, new List<ControlScope>() {ControlScope.None}},
            {EditorControl.Cut, new List<ControlScope>() {ControlScope.None}},
            {EditorControl.Undo, new List<ControlScope>() {ControlScope.None}},
            {EditorControl.Redo, new List<ControlScope>() {ControlScope.None}}
        };

        //
        private EditorControlEventArgs CreateEditorControlEventArgs(SemanticValue semantics)
        {
            try
            {
                EditorControl type = GetEditorControl(semantics, "Type");
                ControlScope scope = GetControlScope(semantics, "Scope");
                IList<ControlScope> scopes;
                if (editorControlMap.TryGetValue(type, out scopes))
                {
                    int? number = GetNumber(semantics, "Number");
                    if (number != null)
                        return new EditorControlEventArgs(type, scope, (int)number);
                    else
                        return new EditorControlEventArgs(type, scope);
                }
                return null;
            }
            catch (Exception)
            {
                return null;
            }
        }

        private int? GetNumber(SemanticValue semantics, string key)
        {
            SemanticValue outSemantic;
            if (TryGetSemanticValue(semantics, key, out outSemantic))
            {
                int outInt;
                if (int.TryParse(outSemantic.Value.ToString(), out outInt))
                {
                    return outInt;
                }
            }
            return null;
        }

        private EditorControl GetEditorControl(SemanticValue semantics, string key)
        {
            SemanticValue outSemantic;
            if (TryGetSemanticValue(semantics, key, out outSemantic))
            {
                return (EditorControl)Enum.Parse(typeof(EditorControl), outSemantic.Value.ToString());
            }
            throw new ArgumentException();
        }

        private ControlScope GetControlScope(SemanticValue semantics, string key)
        {
            SemanticValue outSemantic;
            if (TryGetSemanticValue(semantics, key, out outSemantic))
            {
                return (ControlScope)Enum.Parse(typeof(ControlScope), outSemantic.Value.ToString());
            }
            return ControlScope.None;
        }


        private bool TryGetSemanticValue(SemanticValue semanticValue, string key, out SemanticValue value)
        {
            value = null;
            bool found = semanticValue.ContainsKey(key);
            if (found)
            {
                value = semanticValue[key];
            }
            return found;
        }
    }

    public enum EditorControl
    {
        GoTo, New, Save, Close, Select, Delete, Copy, Paste, Cut, Find, Replace, Undo, Redo
    }

    public enum ControlScope
    {
        All, File, Line, Tab, As, NotePad, None
    }
}
